﻿Imports Microsoft.WindowsAPICodePack.Dialogs
Imports Microsoft.Win32
Imports System.ComponentModel
Imports System.Collections.Concurrent
Imports System.Text.RegularExpressions
Imports System.Deployment.Application
Imports MostRecentlyUsed.MRUItems

Friend Class Helper
    Private Sub New()
    End Sub

    Private Const MAX_PATH = 260
    Private Declare Function GetShortPathName Lib "kernel32" Alias "GetShortPathNameA" (ByVal lpszLongPath As String, ByVal lpszShortPath As String, ByVal cchBuffer As Integer) As Integer

    Public Enum RecentType
        Project
        File
    End Enum

    Public Enum ProjectType
        VisualStudio
        SqlServerManagementStudio
        Office
    End Enum

    Public Enum FrameworkSdkVersion
        Framework35 ' also include 2.0
        Framework40
    End Enum

#Region "Display Exceptions and Messages"

    Friend Shared Sub DisplayException(ByVal exceptionToDisplay As Exception, ByVal parentControl As Control, ByVal caption As String)
        DisplayException(exceptionToDisplay, GetParentForm(parentControl), caption)
    End Sub

    Friend Shared Sub DisplayException(ByVal exceptionToDisplay As Exception, ByVal parentForm As Form, ByVal caption As String)
        Dim bDisplayed As Boolean = False
        Try
            Using oTaskDialog As New MRUTaskDialog(parentForm, True)
                oTaskDialog.Text = exceptionToDisplay.Message
                oTaskDialog.InstructionText = "An Exception Occurred"
                oTaskDialog.Icon = TaskDialogStandardIcon.Error
                oTaskDialog.Cancelable = False
                oTaskDialog.Caption = caption
                oTaskDialog.StartupLocation = TaskDialogStartupLocation.CenterOwner
                oTaskDialog.DetailsExpanded = False
                oTaskDialog.DetailsCollapsedLabel = "Show Exception StackTrace"
                oTaskDialog.DetailsExpandedLabel = "Hide Exception StackTrace"
                oTaskDialog.DetailsExpandedText = exceptionToDisplay.StackTrace

                oTaskDialog.Show()
                bDisplayed = True
                oTaskDialog.Close()
            End Using
        Catch
            If Not bDisplayed Then
                MessageBox.Show(exceptionToDisplay.ToString(), "Exception Occurred", MessageBoxButtons.OK, MessageBoxIcon.Error)
            End If
        End Try
    End Sub

    Friend Shared Sub DisplayMessage(ByVal parentControl As Control, ByVal text As String, ByVal caption As String)
        DisplayMessage(GetParentForm(parentControl), text, caption)
    End Sub

    Friend Shared Sub DisplayMessage(ByVal parentForm As Form, ByVal text As String, ByVal caption As String)
        Dim bDisplayed As Boolean = False
        Try
            Using oTaskDialog As New MRUTaskDialog(parentForm, True)
                oTaskDialog.Text = text
                oTaskDialog.InstructionText = caption
                oTaskDialog.Icon = TaskDialogStandardIcon.Information
                oTaskDialog.Cancelable = False
                oTaskDialog.Caption = caption
                oTaskDialog.StartupLocation = TaskDialogStartupLocation.CenterOwner
                oTaskDialog.DetailsExpanded = False

                oTaskDialog.Show()
                bDisplayed = True
                oTaskDialog.Close()
            End Using
        Catch
            If Not bDisplayed Then
                MessageBox.Show(text, caption, MessageBoxButtons.OK, MessageBoxIcon.Information)
            End If
        End Try
    End Sub

    ''' <summary>
    ''' Displays a message to the user with custom buttons for the response.
    ''' </summary>
    ''' <param name="parentForm">The parent form.</param>
    ''' <param name="textToDisplay">The text to display.</param>
    ''' <param name="caption">The caption.</param>
    ''' <param name="textForButtons">The text for buttons.</param>
    ''' <returns>The text of the selected button</returns>
    Friend Shared Function DisplayMessageWithCustomButtons(ByVal parentForm As Form, ByVal textToDisplay As String, ByVal caption As String, ByVal textForButtons As List(Of String)) As String
        Dim bDisplayed As Boolean = False
        Try
            Using oTaskDialog As New MRUTaskDialog(parentForm, True)
                oTaskDialog.Text = textToDisplay
                oTaskDialog.InstructionText = caption
                oTaskDialog.Icon = TaskDialogStandardIcon.Information
                oTaskDialog.Cancelable = False
                oTaskDialog.Caption = caption
                oTaskDialog.StartupLocation = TaskDialogStartupLocation.CenterOwner
                oTaskDialog.DetailsExpanded = False

                Dim szSelectedButtonText As String = Nothing
                For Each szButtonText As String In textForButtons

                    Dim oButton As TaskDialogButton = New TaskDialogButton()
                    oButton.Text = szButtonText

                    AddHandler oButton.Click, Sub(sender As Object, e As EventArgs)
                                                  Dim oTDB As TaskDialogButton = DirectCast(sender, TaskDialogButton)
                                                  szSelectedButtonText = oTDB.Text
                                                  Dim oDialog As TaskDialog = DirectCast(oTDB.HostingDialog, TaskDialog)
                                                  oDialog.Close(TaskDialogResult.Ok)
                                              End Sub
                    oTaskDialog.Controls.Add(oButton)
                Next


                Dim oResult As TaskDialogResult = oTaskDialog.Show()
                bDisplayed = True
                If (oResult <> TaskDialogResult.Ok) Then
                    oTaskDialog.Close()
                    Return String.Empty
                End If

                Return szSelectedButtonText
            End Using
        Catch
            If Not bDisplayed Then
                MessageBox.Show(textToDisplay, caption, MessageBoxButtons.OK, MessageBoxIcon.Information)
            End If
            Return String.Empty
        End Try
    End Function

#End Region

    Public Shared Function GetAdditionalProjectItems(ByVal eProjectType As ProjectType, ByVal eRecentType As RecentType, ByVal numberOfCurrentItems As Integer, ByVal version As String) As RecentlyUsedItems
        Dim iMaxAdditionalItemsToStore As Integer = GetMaxItemsToStore(eProjectType) + numberOfCurrentItems
        Return New RecentlyUsedItems(eProjectType, eRecentType, version, iMaxAdditionalItemsToStore)
    End Function

    Public Shared Function GetMRUList(Of T As {New, BaseMRUItem})(ByVal eProjectType As ProjectType, ByVal eRecentType As RecentType, ByVal szRegPath As String, ByVal szVersion As String) As List(Of T)
        Dim oMRUList As New List(Of T)()

        Dim oRegKey As RegistryKey = Nothing
        Try
            If (szRegPath.StartsWith("S-", StringComparison.OrdinalIgnoreCase)) Then
                oRegKey = Registry.Users.OpenSubKey(szRegPath)
            Else
                oRegKey = Registry.CurrentUser.OpenSubKey(szRegPath)
            End If

            If (oRegKey IsNot Nothing) Then
                Dim aszValueNames As String() = oRegKey.GetValueNames()
                For Each szName As String In aszValueNames
                    Dim szItemText As String = oRegKey.GetValue(szName).ToString()
                    Dim oMRUItem As New T()
                    If oMRUItem.Populate(szName, szItemText) Then oMRUList.Add(oMRUItem)
                Next
            End If
        Finally
            If oRegKey IsNot Nothing Then
                oRegKey.Close()
            End If
        End Try

        If (New T()).SupportsExtendedItems Then
            Dim oExtendedMRUItems As RecentlyUsedItems = GetAdditionalProjectItems(eProjectType, eRecentType, oMRUList.Count, szVersion)

            Dim oExtendedMRUItemsList As New List(Of BaseMRUItem)()
            For Each szItemText As String In oExtendedMRUItems
                Dim oItem As T = New T()
                If oItem.Populate(Constants.EXTENDED_MRU_ITEM, szItemText) Then oExtendedMRUItemsList.Add(oItem)
            Next

            ' reversing so that the items get added in order of use...
            oMRUList.Reverse()
            For Each oMRUItem As BaseMRUItem In oMRUList
                Dim bAddOrUpdateItem As Boolean = True

                For Each oExtendedMRUItem As ProjectMRUItem In oExtendedMRUItemsList
                    If (String.Compare(oExtendedMRUItem.Path, oMRUItem.Path, True) <> 0) Then
                        ' item not found, if we never find it we'll add it later 
                        Continue For
                    End If

                    If oExtendedMRUItem.Pinned <> oMRUItem.Pinned Then
                        ' item found but the pinned status changed, remove from the extended list and add again later
                        oExtendedMRUItems.Remove(oExtendedMRUItem.ItemText)
                    Else
                        ' we found the item and it had not changed
                        bAddOrUpdateItem = False
                    End If

                    ' we found the item
                    Exit For
                Next

                If bAddOrUpdateItem Then
                    oExtendedMRUItems.AddOrUpdate(oMRUItem.ItemText)
                End If
            Next

            ' reversing again so that the items get displayed in order of use...
            oMRUList.Reverse()

            'where the magic theoretically happens
            For Each oExtendedMRUItem As BaseMRUItem In oExtendedMRUItemsList
                Dim bFound As Boolean = False
                For Each oVSProjectMRUItem As BaseMRUItem In oMRUList
                    If (String.Compare(oExtendedMRUItem.Path, oVSProjectMRUItem.Path, True) = 0) Then
                        bFound = True
                        Exit For
                    End If
                Next
                If False = bFound Then
                    oMRUList.Add(oExtendedMRUItem)
                End If
            Next
        End If
        Return oMRUList
    End Function

#Region "Max items to store methods"

    Public Shared Function GetMaxItemsToStore(eProjectType As ProjectType) As Integer
        Select Case eProjectType
            Case ProjectType.VisualStudio
                Return My.Settings.MaxAdditionalItemsToStore
            Case ProjectType.SqlServerManagementStudio
                Return My.Settings.MaxAdditionalSSMSItemsToStore
            Case Else
                Throw New ArgumentException("Project type not recognized: " + eProjectType)
        End Select
    End Function

    Public Shared Sub SaveMaxItemsToStore(eProjectType As ProjectType, iCount As Integer)
        Select Case eProjectType
            Case ProjectType.VisualStudio
                My.Settings.MaxAdditionalItemsToStore = iCount
            Case ProjectType.SqlServerManagementStudio
                My.Settings.MaxAdditionalSSMSItemsToStore = iCount
            Case Else
                Throw New ArgumentException("Project type not recognized: " + eProjectType)
        End Select
    End Sub

#End Region

#Region "Code file extension methods"

    Public Shared Function GetCodeFileExtensions(eProjectType As ProjectType) As List(Of String)
        Dim szExtensions As String

        Select Case eProjectType
            Case ProjectType.VisualStudio
                szExtensions = My.Settings.CodeFileExtensions
            Case ProjectType.SqlServerManagementStudio
                szExtensions = My.Settings.CodeFileExtensionsSSMS
            Case Else
                Throw New ArgumentException("Project type not recognized: " + eProjectType)
        End Select

        If (String.IsNullOrWhiteSpace(szExtensions)) Then
            szExtensions = String.Empty
        End If

        Dim aszExtensions As String() = szExtensions.ToUpper().Split(",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries)

        If (aszExtensions.Length > 0) Then
            Return New List(Of String)(aszExtensions)
        End If

        Select Case eProjectType
            Case ProjectType.VisualStudio
                Return New List(Of String)({"CS", "VB", "SQL", "ASPX", "CONFIG"})
            Case ProjectType.SqlServerManagementStudio
                Return New List(Of String)({"SQL"})
            Case Else
                Throw New ArgumentException("Project type not recognized: " + eProjectType)
        End Select

    End Function

    Public Shared Sub SaveCodeFileExtensions(eProjectType As ProjectType, oExtensions As List(Of String))
        Dim szExtensions = String.Join(",", oExtensions)
        Select Case eProjectType
            Case ProjectType.VisualStudio
                My.Settings.CodeFileExtensions = szExtensions.ToUpper()
            Case ProjectType.SqlServerManagementStudio
                My.Settings.CodeFileExtensionsSSMS = szExtensions.ToUpper()
            Case Else
                Throw New ArgumentException("Project type not recognized: " + eProjectType)
        End Select
        My.Settings.Save()
    End Sub

#End Region

#Region "Search TFS Extensions To Treat As Directory"

    Public Shared Function GetTFSExtensionsToTreatAsDirectory() As List(Of String)
        Dim szExtensions As String = My.Settings.SearchTFSExtensionsToTreatAsDirectory

        If (String.IsNullOrWhiteSpace(szExtensions)) Then
            szExtensions = String.Empty
        End If

        Dim aszExtensions As String() = szExtensions.ToUpper().Split(",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries)

        If (aszExtensions.Length > 0) Then
            Return New List(Of String)(aszExtensions)
        End If

        Return New List(Of String)({"SLN", "VBPROJ", "CSPROJ", "SSMSSLN"})
    End Function

    Public Shared Sub SaveTFSExtensionsToTreatAsDirectory(oExtensions As List(Of String))
        Dim szExtensions = String.Join(",", oExtensions)
        My.Settings.SearchTFSExtensionsToTreatAsDirectory = szExtensions.ToUpper()
        My.Settings.Save()
    End Sub

#End Region

#Region "Friend helper methods"

    Friend Shared Sub GetLastestFromSourceControlDirectory(ByVal localDirectory As String, ByVal fileName As String)
        Dim szTFPath As String = GetPathToTFExe()

        Dim szSourceControlDirectory = ConvertLocalPathToSourceControlPath(szTFPath, localDirectory)

        Using oProcess = New Process()
            Dim oProcessStartInfo As New ProcessStartInfo(szTFPath)
            oProcessStartInfo.CreateNoWindow = True
            oProcessStartInfo.UseShellExecute = False
            oProcessStartInfo.Arguments = String.Format("get ""{0}"" /version:T /all /recursive /noprompt", If(String.IsNullOrWhiteSpace(fileName), szSourceControlDirectory, szSourceControlDirectory & "/" & fileName))
            oProcessStartInfo.ErrorDialog = False
            oProcessStartInfo.RedirectStandardOutput = True
            oProcessStartInfo.RedirectStandardError = True
            oProcessStartInfo.RedirectStandardInput = False
            oProcessStartInfo.WorkingDirectory = localDirectory
            oProcess.StartInfo = oProcessStartInfo
            oProcess.Start()

            ' Do not wait for the child process to exit before reading to the end of its redirected stream.
            ' Read the output stream first and then wait. This avoids a deadlock condition. 
            Dim szOutput As String = oProcess.StandardOutput.ReadToEnd()
            Dim szError As String = oProcess.StandardError.ReadToEnd()
            oProcess.WaitForExit()
            If oProcess.ExitCode <> 0 Then
                Throw New ApplicationException(String.Format("Error getting project from source control. Exit Code: '{3}' Path: '{4}'{0}{0}Error text:{0}{1}{0}{0}Additional Text:{0}{2}", Environment.NewLine, szError, szOutput, oProcess.ExitCode, szSourceControlDirectory))
            End If
        End Using
    End Sub

    Friend Shared Function ReplaceCaseInsensitive(szInput As String, szTextToReplace As String, szReplacementText As String) As String
        Return Regex.Replace(szInput, Regex.Escape(szTextToReplace), szReplacementText, RegexOptions.IgnoreCase)
    End Function

    Friend Shared Function GetPathToTFExe() As String
        Dim szVSToolsPath As String = If(Environment.GetEnvironmentVariable("VS120COMNTOOLS", EnvironmentVariableTarget.Machine), _
                                        If(Environment.GetEnvironmentVariable("VS110COMNTOOLS", EnvironmentVariableTarget.Machine), _
                                         If(Environment.GetEnvironmentVariable("VS100COMNTOOLS", EnvironmentVariableTarget.Machine), _
                                             If(Environment.GetEnvironmentVariable("VS90COMNTOOLS", EnvironmentVariableTarget.Machine), Environment.GetEnvironmentVariable("VS80COMNTOOLS", EnvironmentVariableTarget.Machine))
                                            )
                                          )
                                        )

        Dim szTFPath As String = Path.Combine(szVSToolsPath, "..\IDE\TF.exe")

        If (Not File.Exists(szTFPath)) Then
            Throw New FileNotFoundException("Can't find TF.exe", szTFPath)
        End If

        Return szTFPath
    End Function

    Friend Shared Sub OpenFile(szFile As String)
        If Not File.Exists(szFile) Then
            Throw New FileNotFoundException("Unable to find project file.", szFile)
        End If

        Using oProcess As New Process()
            oProcess.StartInfo = New ProcessStartInfo(szFile)
            oProcess.Start()
        End Using
    End Sub

    Friend Shared Sub OpenDirectory(szDirectory As String, Optional bCheckExistance As Boolean = True)
        If (bCheckExistance And Not Directory.Exists(szDirectory)) Then
            Throw New DirectoryNotFoundException("Directory could not be found: " + szDirectory)
        End If

        Try
            Using oProcess = New Process()
                oProcess.StartInfo = New ProcessStartInfo(szDirectory)
                oProcess.Start()
            End Using
        Catch oExp As Win32Exception
            Throw New ApplicationException(String.Format("Problem opening directory '{0}': \r\n\r\n{1}", szDirectory, oExp))
        End Try
    End Sub

    Friend Shared Function GetFilesInDirectory(szDirectory As String, oFilterList As List(Of String), Optional bStopAtFirst As Boolean = False) As List(Of String)
        Dim oFiles As New ConcurrentBag(Of String)()

        Parallel.ForEach(oFilterList, Sub(szExtension, oLoopState)
                                          For Each szFile As String In Directory.GetFiles(szDirectory, "*." + szExtension, SearchOption.TopDirectoryOnly)
                                              oFiles.Add(szFile)
                                              If (bStopAtFirst And Not oLoopState.IsStopped) Then
                                                  ' need to stop all threads
                                                  oLoopState.Stop()
                                              End If

                                              ' exit the loop if any thread has indicated it is done
                                              If (oLoopState.IsStopped) Then Exit For
                                          Next
                                      End Sub)
        Return oFiles.ToList()
    End Function

    Friend Shared Function ConvertLocalPathToSourceControlPath(szTFPath As String, szLocalDirectory As String) As String
        Dim szSourceControlPath
        ' Get the working folder mapping
        Using oProcess = New Process()
            Dim oProcessStartInfo As New ProcessStartInfo(szTFPath)
            oProcessStartInfo.CreateNoWindow = True
            oProcessStartInfo.UseShellExecute = False
            oProcessStartInfo.Arguments = String.Format("workfold ""{0}""", szLocalDirectory)
            oProcessStartInfo.ErrorDialog = False
            oProcessStartInfo.RedirectStandardOutput = True
            oProcessStartInfo.RedirectStandardError = True
            oProcessStartInfo.RedirectStandardInput = False
            oProcessStartInfo.WorkingDirectory = szLocalDirectory
            oProcess.StartInfo = oProcessStartInfo
            oProcess.Start()

            ' Do not wait for the child process to exit before reading to the end of its redirected stream.
            ' Read the output stream first and then wait. This avoids a deadlock condition. 
            Dim szOutput As String = oProcess.StandardOutput.ReadToEnd()
            Dim szError As String = oProcess.StandardError.ReadToEnd()
            oProcess.WaitForExit()
            If oProcess.ExitCode <> 0 Then
                Throw New ApplicationException(String.Format("Error getting source control path for folder. Exit Code: '{3}' Path: '{4}'{0}{0}Error text:{0}{1}{0}{0}Additional Text:{0}{2}", Environment.NewLine, szError, szOutput, oProcess.ExitCode, szLocalDirectory))
            End If

            Dim oMatch = Regex.Match(szOutput, "^[\s]*(?<ServerPath>\$/[^:]*):[\s]*(?<LocalPath>.*)", RegexOptions.Multiline)
            If (Not oMatch.Success) Then Throw New ApplicationException(String.Format("Unable to determine root TFS server path from TF.exe output:{0}{1}", Environment.NewLine, szOutput))

            szSourceControlPath = oMatch.Groups("ServerPath").Value.Trim()
        End Using

        If (Not szSourceControlPath.StartsWith("$")) Then
            Throw New ApplicationException("Unable to determine root TFS server path.")
        End If

        Return szSourceControlPath
    End Function

    Friend Shared Function GetDataDirectory() As String
        Dim szDirectory As String
        If (ApplicationDeployment.IsNetworkDeployed) Then
            szDirectory = ApplicationDeployment.CurrentDeployment.DataDirectory
        Else
            szDirectory = Path.Combine(Environment.GetEnvironmentVariable("Temp", EnvironmentVariableTarget.User), "MRU")
            Directory.CreateDirectory(szDirectory)
        End If

        If (Not Directory.Exists(szDirectory)) Then Return String.Empty

        Return szDirectory
    End Function

    Friend Shared Function GetFrameworkSDKPath(ByVal version As FrameworkSdkVersion) As String
        Dim szReturnValue As String = ""

        Select Case version
            Case FrameworkSdkVersion.Framework35
                ' look for framework 3.5 first
                szReturnValue = Registry.GetValue("HKEY_LOCAL_MACHINE\SOFTWARE\MICROSOFT\MICROSOFT SDKs", "CurrentInstallFolder", String.Empty)

                If (String.IsNullOrWhiteSpace(szReturnValue)) Then
                    ' Now look for framework 2.0
                    szReturnValue = Registry.GetValue("HKEY_LOCAL_MACHINE\SOFTWARE\MICROSOFT\.NETFramework", "SDKInstallRootv2.0", String.Empty)
                End If

                If (Not String.IsNullOrWhiteSpace(szReturnValue)) Then szReturnValue = Path.Combine(szReturnValue, "bin")
                Exit Select
            Case FrameworkSdkVersion.Framework40
                ' look for framework 4.0 first
                szReturnValue = Registry.GetValue("HKEY_LOCAL_MACHINE\SOFTWARE\MICROSOFT\MICROSOFT SDKs\Windows", "CurrentInstallFolder", String.Empty)

                If (Not String.IsNullOrWhiteSpace(szReturnValue)) Then szReturnValue = Path.Combine(szReturnValue, "bin\NETFX 4.0 Tools")
                Exit Select
        End Select

        If (Not String.IsNullOrWhiteSpace(szReturnValue)) AndAlso (Not Directory.Exists(szReturnValue)) Then szReturnValue = String.Empty

        Return szReturnValue
    End Function

    Friend Shared Function ShortPathName(ByVal longPathFileName As String) As String
        Dim iLength As Integer
        Dim szShortPath As String = Space(MAX_PATH)

        iLength = GetShortPathName(longPathFileName, szShortPath, Len(szShortPath))

        If iLength Then Return Strings.Left(szShortPath, iLength)

        ' when in doubt, return the long path
        Return longPathFileName

    End Function

#Region "DeleteNoError methods"

    ''' <summary>Deletes a file, but ignore errors.</summary>
    ''' <param name="file">Path and file name to delete.</param>
    ''' <returns>True if the file was deleted.  False means that the file couldn't be deleted, doesn't exist, the 
    ''' path is invalid, or some other error.  You can't determine why it failed.</returns>
    Friend Shared Function DeleteNoError(file As String) As Boolean
        Try
            IO.File.Delete(file)
            Return True
        Catch
            Return False
        End Try
    End Function

    ''' <summary>Deletes all the files specified in the collection.  Errors are not thrown if the file is readonly 
    ''' or missing.</summary>
    ''' <param name="filesToDelete">Collection of files to delete.  Can be from a string [], StringCollection, etc.</param>
    ''' <returns>True if all files where deleted.  False if any file couldn't be deleted.</returns>
    ''' <exception cref="System.InvalidCastException">InvalidCastException if collection contains non string 
    ''' data.</exception>
    Friend Shared Function DeleteNoError(filesToDelete As ICollection) As Boolean
        Dim bRes As Boolean = True
        For Each szFile As String In filesToDelete
            If False = DeleteNoError(szFile) Then
                bRes = False
            End If
        Next
        Return bRes
    End Function

#End Region

#Region "Validate File/Path Methods"

    ''' <summary>
    ''' Validates the path. Throws an exception if the path is invalid.
    ''' </summary>
    ''' <param name="pathToValidate">path to validate</param>
    Public Shared Sub ValidatePath(pathToValidate As String)
        If pathToValidate Is Nothing Then
            pathToValidate = String.Empty
        End If

        pathToValidate = pathToValidate.Trim()
        If (pathToValidate.Length > 0) AndAlso (Directory.Exists(pathToValidate)) Then
            'Valid path
            Return
        End If
        Throw New ApplicationException(String.Format("Path '{0}' specified does not exist.", pathToValidate))
    End Sub

    ''' <summary>
    ''' Validates the file. Throws an exception if the file is invalid.
    ''' </summary>
    ''' <param name="fileToValidate">path to validate</param>
    Public Shared Sub ValidateFile(fileToValidate As String)
        If fileToValidate Is Nothing Then
            fileToValidate = String.Empty
        End If

        fileToValidate = fileToValidate.Trim()
        If (fileToValidate.Length > 0) AndAlso (File.Exists(fileToValidate)) Then
            'Valid path
            Return
        End If
        Throw New FileNotFoundException(String.Format("File '{0}' specified does not exist or is invalid.", fileToValidate))
    End Sub

    ''' <summary>
    ''' Validates and returns the path.  Throws an exception if the path is invalid.
    ''' </summary>
    ''' <param name="path">path</param>
    ''' <returns>A validated path string</returns>
    Public Shared Function ValidateAndReturnPath(path As String) As String
        ValidatePath(path)
        Return path
    End Function

    ''' <summary>
    ''' Validates and returns the file.  Throws an exception if the file is not found or invalid.
    ''' </summary>
    ''' <param name="file">file</param>
    ''' <returns></returns>
    Public Shared Function ValidateAndReturnFile(file As String) As String
        ValidateFile(file)
        Return file
    End Function

#End Region

#End Region

#Region "SetReadOnlyAttribute Methods"

    ''' <summary>
    ''' Sets the read only attribute.
    ''' </summary>
    ''' <param name="filePath">file</param>
    ''' <param name="makeReadOnly">if set to <c>true</c> [read only].</param>
    Public Shared Sub SetReadOnlyAttribute(ByVal filePath As String, ByVal makeReadOnly As Boolean)
        Const READ_ONLY_ATTRIBUTE As FileAttributes = FileAttributes.ReadOnly

        If makeReadOnly = False Then
            File.SetAttributes(filePath, File.GetAttributes(filePath) And (Not READ_ONLY_ATTRIBUTE))
        Else
            File.SetAttributes(filePath, File.GetAttributes(filePath) Or (READ_ONLY_ATTRIBUTE))
        End If
    End Sub

    ''' <summary>
    ''' Sets the read only attribute.
    ''' </summary>
    ''' <param name="filePaths">files</param>
    ''' <param name="makeReadOnly">if set to <c>true</c> [read only].</param>
    Public Shared Sub SetReadOnlyAttribute(ByVal filePaths As String(), ByVal makeReadOnly As Boolean)
        For Each szFile As String In filePaths
            SetReadOnlyAttribute(szFile, makeReadOnly)
        Next
    End Sub

    ''' <summary>
    ''' Sets the read only attribute.
    ''' </summary>
    ''' <param name="filePaths">files</param>
    ''' <param name="makeReadOnly">if set to <c>true</c> [read only].</param>
    Public Shared Sub SetReadOnlyAttribute(ByVal filePaths As ICollection, ByVal makeReadOnly As Boolean)
        For Each szFile As String In filePaths
            SetReadOnlyAttribute(szFile, makeReadOnly)
        Next
    End Sub

#End Region

#Region "Private helper methods"

    Private Shared Function GetParentForm(oParentControl As Control) As Form
        Dim oFrmMain As Form = Nothing
        Dim oParent As Control = oParentControl
        While oFrmMain Is Nothing
            oFrmMain = TryCast(oParent, Form)
            oParent = oParent.Parent
        End While

        Return oFrmMain
    End Function

#End Region

End Class
